#!/usr/bin/php
<?php

require_once(dirname(__FILE__) . '/../../luminous.php');

/** Fuzz tester for Luminous.
 * The purpose of the fuzz tester is to just spam random data into the 
 * parser and see whether it returns within the given allowed time.
 * If php times out the script then we raise a warning, because that
 * either means it wasn't allowed long enough, or, more likely, it hit an
 * infinite loop.
 * 
 * We currently test cpp, css, java, js and python because this spans an
 * interesting range: cpp, css and python are all 'stateful'
 */

$time_limit;


$done = false;

$EXIT_STATUS = 0;

$src = "";
$lang = "";
$luminous = null;

function random_source($size=1024000)
{
  $symbols = 'abcdefghijklmnopqrstuvwxyz1234567890!"$%^&*()-_=+#~[]{};:\'@,./<>?` ' . "\t\n\r";
  $s = str_split($symbols);
  $s = array_merge($s, array('if', 'else', 'for', 'while', 'do'));
  $src = "";
  for ($i=0; $i<$size; $i++)
    $src .= $s[rand(0, count($s)-1)];
  return $src;
}


function finish()
{
  global $done, $lang, $src, $EXIT_STATUS, $luminous, $options;
  $msg = "";
  if ($done != true)
  {
    if ($options['trace'])
      $msg = $luminous->Debug();
    else
      $msg = "\nFuzz test failed for language: $lang\nOn source:\n$src";
    $EXIT_STATUS = 1;
  }
  echo $msg;
  exit($EXIT_STATUS);
}

register_shutdown_function('finish');



function test($src_, $l)
{
  global $luminous, $lang, $src, $luminous_grammars;
  
  $luminous = new Luminous();
  $src = $src_;
  $lang = $l;
  $g = $luminous_grammars->GetGrammar($l);
  $out = $luminous->Easy_parse($src, $g);

}






$options = array(
    'trace' => false,
    'srcs' => array(),
    'langs' => array(),
    'time' => 300,
    'size' => '1024000',

  );

function parse_args()
{
  global $argv, $done, $options;
  
  if (in_array('--help', $argv))
  {
    echo <<<EOF
Usage: {$argv[0]} [OPTIONS] [language [FILE]]

This script is a simple noise tester for luminous. The best way to use it is to
let it run the default tests; if one fails it will spam the output to stdout
(you'll want to redirect this to a file) and exit with status 1. A test 
failure means one of two things:
  Luminous hit an infinite loop
  Luminous was slow and didn't manage to complete before the time limit kicked
    in (a false positive)

Therefore after a test fails, the first thing you should do is re-run the test
(on the output, which you should have directed to a file) with an exhaustive 
--time-limit parameter, and using the --trace option. If the time limit is 
still triggered then a stack trace and some of Luminous's internal state
will be printed to stdout.

Options:
  
  --size n
    Size of randomly generated source code (recognises 'K', 'M' suffixes)
    
  --time-limit n 
    time limit (in seconds)
    
  --trace 
    print stack trace from Luminous instead of failed source 
    

EOF;
    $done = true;
    exit(0);
  }
  
  $lang = null;
  $file = null;
  for ($i=1; $i<count($argv); $i++)
  {
    $a = $argv[$i];
    if ($a == '--trace')
      $options['trace'] = true;
    elseif ($a == '--time-limit')
      $options['time'] = (int)($argv[++$i]);
    elseif ($a == '--size')
      $options['size'] = ($argv[++$i]);
    elseif ($lang == null)
      $lang = $a;
    else $file = $a;    
  }
  set_time_limit($options['time']);
  
  if ($file !== null)
  {
    $file_contents = file_get_contents($file);
    $m;
    if (preg_match("/Fuzz test failed for language: (.*)\nOn Source:\n([\s\S]+)/i", $file_contents, $m))
    {
      $options['srcs'][] = $m[2];
      $options['langs'][] = $m[1];
    }
    else
    {
      $options['srcs'][] = file_get_contents($file);
      $options['langs'][] = $lang;
    }
  }
  else
  {
    $langs = array();
    if ($lang !== null) 
      $langs = array($lang);
    else
      $langs = array('cpp', 'java', 'js', 'css', 'python', 'erlang'); 
    $s = $options['size'];
    $unit = $s[strlen($s)-1];
    if ($unit == 'K' || $unit == 'k')
      $s = (int)$s * 1024;
    elseif ($unit == 'M' || $unit == 'm')
      $s = (int)$s * 1024*1024;    
    $src_ = random_source((int)$s);
    foreach($langs as $l)
    {
     
      $options['srcs'][] = $src_;
      $options['langs'][] = $l;
    }
  }
  
}


parse_args();
for($i=0; $i<count($options['srcs']); $i++)
  test($options['srcs'][$i], $options['langs'][$i]);


$done = true;
exit($EXIT_STATUS);
